package com.apes.framework.util;

import com.alibaba.fastjson.annotation.JSONField;
import com.apes.framework.jpa.ApesBean;
import com.apes.framework.jpa.Name;
import com.apes.framework.jpa.repository.CustomRepository;
import com.apes.framework.plugin.log.timer.Timer;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 功能：Map相关工具类
 *
 * @author xul
 * @create 2018-02-01 19:30
 */
@Component
public class MapUtil {

    /*
     * Map构造
     */
    public static Map mapper(Object... items) {
        Assert.isTrue(items.length % 2 == 0, "参数输入数量错误！");
        int i = 0;
        Object k = null;
        Object v;
        Map m = new HashMap();
        for (Object o : items) {
            if (i % 2 == 0) {
                k = o;
            } else {
                v = o;
                if (null != k && null != m) {
                    m.put(k, v);
                }
            }
            i++;
        }
        return m;
    }

    /**
     * JavaBean序列化
     */
    public static <T> T mapped(Object obj, String... excludesFields) {
        try {
            if (obj instanceof Collection) {
                Collection c = (Collection) obj;
                if (c.isEmpty()) return (T) c;
                List rows = new ArrayList();
                for (Object o : c) {
                    if (setValue(rows, o, excludesFields)) continue;
                }
                if (rows == null) return null;
                return (T) rows;
            } else if (obj instanceof Iterable) {
                Iterable iterable = (Iterable) obj;
                if (iterable == null) return null;
                List rows = new ArrayList();
                for (Object o : iterable) {
                    if (setValue(rows, o, excludesFields)) continue;
                }
                if (obj instanceof Page) {
                    Map row = new HashMap();
                    row.put("total", ((Page) obj).getTotalElements());
                    row.put("pages", ((Page) obj).getTotalPages());
                    row.put("page", ((Page) obj).getNumber());
                    row.put("size", ((Page) obj).getSize());
                    row.put("elements", ((Page) obj).getNumberOfElements());
                    row.put("content", rows);
                    return (T) row;
                } else {
                    return (T) rows;
                }
            } else if (obj instanceof Map) {
                Map row = new HashMap();
                ((Map) obj).forEach((k, v) -> {
                    if (v == null) {
                        row.put(k, v);
                    } else if (ReflectUtil.isPrimitive(v.getClass())) {
                        row.put(k, v);
                    } else {
                        row.put(k, mapped(v, excludesFields));
                    }
                });
                return (T) row;
            } else if (obj instanceof ApesBean) {
                Map<String, Object> m = mapped(obj, true, excludesFields);
                if (m == null) return null;
                return (T) m;
            } else {
                return (T) obj;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对象转JavaBean
     */
    public static <T> T getO(Object o, Class<T> clazz) {
        T t = Tools.map(o, clazz);
        if (t instanceof ApesBean) load(t);
        return t;
    }

    private static boolean setValue(List rows, Object o, String... excludesFields) {
        if (o == null) return true;
        if (ReflectUtil.isPrimitive(o.getClass())) {
            rows.add(o);
        } else {
            rows.add(mapped(o, excludesFields));
        }
        return false;
    }

    private static Map<String, Object> mapped(Object obj, boolean isNested, String... excludesFields) throws Exception {
        if (obj == null) return null;
        Map<String, Object> result = new HashMap();
        PropertyDescriptor[] propertyDescriptors = ReflectUtil.getPropertyDescriptors(obj);
        for (PropertyDescriptor property : propertyDescriptors) {
            if (notSerialize(obj, property, isNested, excludesFields)) continue;
            String fieldName = property.getName();
            Object value = null;
            Method getter = property.getReadMethod();
            if (getter != null) {
                if (getter.isAccessible()) {
                    getter.setAccessible(true);
                }
                value = getter.invoke(obj);
            }

            if (value == null) continue;

            if (ReflectUtil.getAnnotation(obj, fieldName, OneToMany.class) != null) {
                Collection c = ((Collection) value);
                if (c == null || c.isEmpty()) continue;
                List rows = new ArrayList();
                for (Object o : c) {
                    Map m = mapped(o, true, excludesFields);
                    rows.add(m);
                }
                result.put(fieldName, rows);
            } else if (ReflectUtil.getAnnotation(obj, fieldName, ManyToMany.class) != null) {
                ManyToMany annotation = ReflectUtil.getAnnotation(obj, fieldName, ManyToMany.class);
                if (!Tools.isNull(annotation.mappedBy())) continue;
                if (annotation.cascade().length == 0) continue;
                Collection c = ((Collection) value);
                if (c.isEmpty()) continue;
                List rows = new ArrayList();
                for (Object o : c) {
                    Map m = mapped(o, true, excludesFields);
                    rows.add(m);
                }
                result.put(fieldName, rows);
            } else if (ReflectUtil.getAnnotation(obj, fieldName, OneToOne.class) != null) {
                Map m = mapped(value, true, excludesFields);
                result.put(fieldName, m);
            } else if (ApesBean.class.isAssignableFrom(value.getClass())) { //ManyToOne 中主数据问题
                Map m = mapped(value, false, excludesFields);
                result.put(fieldName, m);
            } else if (Date.class.isAssignableFrom(value.getClass())) {
                result.put(fieldName, getDateFormat(obj, (Date) value, fieldName));
            } else if (ReflectUtil.isPrimitive(value.getClass())) {
                result.put(fieldName, value);
            } else if (value.getClass().isEnum()) {
                result.put(fieldName, value);
            } else if (fieldName.equals("extProperties")) {
                result.putAll((Map<String, Object>) value);
            } else if (ReflectUtil.getAnnotation(obj, fieldName, Transient.class) != null) {
                result.put(fieldName, mapped(value));
            } else if (!value.getClass().isAssignableFrom(Class.class)) {
                //throw new RuntimeException("实体类型定义错误：" + value);
            }
        }
        return result;
    }

    private static String getDateFormat(Object obj, Date value, String fieldName) {
        String pattern = DateUtil.SHOW_DATETIME_FORMAT;
        JSONField annotation = ReflectUtil.getAnnotation(obj, fieldName, JSONField.class);
        if (annotation != null) {
            String format = annotation.format();
            if (!Tools.isNull(format)) pattern = format;
        }
        return DateUtil.format(value, pattern);
    }

    private static Map<String, Boolean> serializes = new HashMap();

    private static boolean notSerialize(Object obj, PropertyDescriptor property, boolean isNested, String... excludesFields) {
        String fieldName = property.getName();
        if (LogicalUtil.in(fieldName, excludesFields)) {
            return true;
        }
        Class clazz = ReflectUtil.getClass(obj);
        String key = ReflectUtil.concat(clazz.getName(), fieldName, String.valueOf(isNested));
        if (serializes.containsKey(key)) {
            return serializes.get(key);
        } else {
            Boolean result = notSerialize(obj, property, isNested);
            serializes.put(key, result);
            return result;
        }
    }

    private static boolean notSerialize(Object obj, PropertyDescriptor property, boolean isNested) {
        String fieldName = property.getName();

        Annotation annotation = ReflectUtil.getAnnotation(obj, fieldName, JSONField.class);
        if (annotation != null) {
            if (!((JSONField) annotation).serialize()) return true;
        }

        annotation = ReflectUtil.getAnnotation(obj, fieldName, ManyToOne.class);
        if (annotation != null) {
            if (!((ManyToOne) annotation).optional()) return true;
        }

        annotation = ReflectUtil.getAnnotation(obj, fieldName, OneToOne.class);
        if (annotation != null) {
            if (!((OneToOne) annotation).optional()) return true;
        }

        if (fieldName.equalsIgnoreCase("extPropertiesContent")) return true;
        if (fieldName.equalsIgnoreCase("write")) return true;
        if (fieldName.equalsIgnoreCase("load")) return true;

        if (!isNested) {
            annotation = ReflectUtil.getAnnotation(obj, fieldName, Id.class);
            if (annotation != null) return false;

            annotation = ReflectUtil.getAnnotation(obj, fieldName, Name.class);
            if (annotation != null) return false;

            if (!ReflectUtil.isPrimitive(property.getPropertyType())) return true;
            if (!fieldName.equalsIgnoreCase("id") && !fieldName.equalsIgnoreCase("name")) return true;
        }
        return false;
    }

    /*
     * 刷新对象
     */
    @Timer("刷新对象关联的主数据")
    public void refresh(Object entity) {
        /** 刷新对象关联的主数据 */
        load(entity);
    }

    /**
     * 功能：刷新对象关联的主数据
     */
    private static void load(Object obj) {
        if (obj == null) return;
        PropertyDescriptor[] propertyDescriptors = ReflectUtil.getPropertyDescriptors(obj);
        if (propertyDescriptors == null) return;
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            Class type = descriptor.getPropertyType();
            if (ReflectUtil.isPrimitive(type) || type == Date.class || type == Class.class) {
                continue;
            }
            String fieldName = descriptor.getName();

            Annotation annotation = ReflectUtil.getAnnotation(obj, fieldName, Transient.class);
            if (annotation != null) continue;
            Method getter = descriptor.getReadMethod();
            Object value = null;
            try {
                if (getter != null) {
                    if (getter.isAccessible()) {
                        getter.setAccessible(true);
                    }
                    value = getter.invoke(obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (value == null) continue;
            if (value instanceof Collection && ((Collection) value).isEmpty()) continue;

            annotation = ReflectUtil.getAnnotation(obj, fieldName, OneToMany.class);
            if (annotation != null && ((OneToMany) annotation).cascade().length > 0) { /** 级联 **/
                Collection c = ((Collection) value);
                if (c.isEmpty()) continue;
                for (Object o : c) {
                    load(o);
                }
                continue;
            }

            annotation = ReflectUtil.getAnnotation(obj, fieldName, ManyToOne.class);
            if (value instanceof ApesBean && annotation != null && ((ManyToOne) annotation).optional()) {
                setValue(obj, descriptor, (ApesBean) value);
                continue;
            }

            annotation = ReflectUtil.getAnnotation(obj, fieldName, OneToOne.class);
            if (value instanceof ApesBean && annotation != null && ((OneToOne) annotation).optional()) {
                if (((OneToOne) annotation).cascade().length == 0) { /** 非级联 **/
                    setValue(obj, descriptor, (ApesBean) value);
                    continue;
                } else { /** 级联 **/
                    load(value);
                }
            }
        }
    }

    private static void setValue(Object obj, PropertyDescriptor descriptor, ApesBean value) {
        Object entity = getEntity(value);
        if (entity == null) return;
        Method writer = descriptor.getWriteMethod();
        try {
            if (writer.isAccessible()) {
                writer.setAccessible(true);
            }
            writer.invoke(obj, entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Object getEntity(ApesBean bean) {
        Class domainClass = JpaUtil.getDomainClass(bean);
        CustomRepository repository = JpaUtil.getRepository(domainClass.getName());
        if (repository == null) {
            throw new RuntimeException("实体：" + domainClass.getSimpleName() + " 没有定义 Repository ！");
        }
        Object id = JpaUtil.getId(bean);
        if (StringUtils.isEmpty(id)) return null;
        Object entity = repository.findOne((Serializable) id);
        if (entity == null) {
            throw new RuntimeException("实体" + domainClass.getSimpleName() + " 值：" + id + "在系统中不存在。");
        }
        return entity;
    }
}
